﻿#include "pch.h"
#include "sbhs.h"
#include "sbctn.h"
#include "kh_compiler.h"
#include "kh_code.h"
#include "extend/sbmnehs.h"
#include "extend/sberror.h"

//
extern void _kh_compiler_init(void);
extern void _kh_compiler_disp(void);

extern kuint k_hlib_sys(kHsm* hsm);

//////////////////////////////////////////////////////////////////////////
// 관리

//
static void _k_hs_type_init(void);

//
static struct kImplHs
{
	volatile kcham		islock;

	kMutex*				lock;
	kCond*				cond;
} k_impl_hs =
{
	FALSE,
	NULL,
	NULL,
};

//
void _k_hs_init(void)
{
	k_impl_hs.lock = k_mutex_new();
	k_impl_hs.cond = k_cond_new();

	_k_hs_type_init();
	_kh_compiler_init();
}

//
void _k_hs_disp(void)
{
	_kh_compiler_disp();

	k_cond_delete(k_impl_hs.cond);
	k_mutex_delete(k_impl_hs.lock);
}

//
void k_hs_enter(void)
{
	// 이걸 k_once로 바꾸면 어떨까?

	k_mutex_enter(k_impl_hs.lock);

	while (k_atomget(&k_impl_hs.islock))
	{
		//k_dmesg("HSM is locked.\n");
		k_cond_wait(k_impl_hs.cond, k_impl_hs.lock);
	}

	k_atomset(&k_impl_hs.islock, TRUE);

	k_mutex_leave(k_impl_hs.lock);
}

//
void k_hs_leave(void)
{
	if (!k_atomget(&k_impl_hs.islock))
		return;

	k_mutex_enter(k_impl_hs.lock);
	k_atomset(&k_impl_hs.islock, FALSE);
	k_cond_signal(k_impl_hs.cond);
	k_mutex_leave(k_impl_hs.lock);
}


//////////////////////////////////////////////////////////////////////////
// hsm

//
kType k_hsm_type(void)
{
	return KTYPE_HSM;
}

//
static kBase* _k_hsm_cnst(kpointer ptr)
{
	khRealHsm* self = (khRealHsm*)ptr;

	self->lock = k_mutex_new();
	
	self->cstk = 4096;
	self->super = k_redtbl_new();

#if _SB_DEBUG_
	self->flags = KHSF_TRACE_EXCEPTION;
	self->flags |= KHSF_BREAK_EXCEPTION;
#endif

	k_arr_init(kUintArr, &self->libs, 0);

	return (kBase*)self;
}

//
static kpointer _k_hsm_disp(kBase* ptr)
{
	khRealHsm* self = (khRealHsm*)ptr;
	khRunInfo* ri;
	khCallInfo* ci;

	for (ri = self->rri; ri;)
	{
		khRunInfo* nri = ri->next;
		k_delete(ri->data.data);
		k_delete(ri);
		ri = nri;
	}

	for (ci = self->rci; ci;)
	{
		khCallInfo* nci = ci->next;
		k_delete(ci);
		ci = nci;
	}

	k_base_unload((kBase*)self->super);

	k_mutex_delete(self->lock);

	k_arr_disp(kUintArr, &self->libs);

	return self;
}

//
kRedTbl* k_hsm_get_super(kHsm* ptr)
{
	khRealHsm* self = (khRealHsm*)ptr;

	return self->super;
}

//
kint k_hsm_get_flags(kHsm* ptr, kint mask)
{
	khRealHsm* self = (khRealHsm*)ptr;

	return K_OMASK(self->flags, mask);
}

//
kint k_hsm_get_stack_count(kHsm* ptr)
{
	khRealHsm* self = (khRealHsm*)ptr;

	return self->cstk;
}

//
ksize_t k_hsm_get_stack_size(kHsm* ptr)
{
	khRealHsm* self = (khRealHsm*)ptr;

	return self->cstk * sizeof(kVar);
}

//
kint k_hsm_set_flags(kHsm* ptr, kint value)
{
	khRealHsm* self = (khRealHsm*)ptr;
	kint ret = self->flags;

	self->flags = value;

	return ret;
}

//
kint k_hsm_set_stack_count(kHsm* ptr, kint count)
{
	khRealHsm* self = (khRealHsm*)ptr;
	kint ret = self->cstk;

	self->cstk = count;

	return ret;
}

//
ksize_t k_hsm_set_stack_size(kHsm* ptr, ksize_t size)
{
	khRealHsm* self = (khRealHsm*)ptr;
	kint ret = self->cstk * sizeof(kVar);

	self->cstk = (kint)(size / sizeof(kVar));

	return ret;
}

//
kcham k_hsm_find_var(kHsm* ptr, const char* find, kVar* value)
{
	khRealHsm* self = (khRealHsm*)ptr;

	return k_redtbl_find(self->super, find, value);
}

//
kcham k_hsm_get_var(kHsm* ptr, const kVar* key, kVar* value)
{
	khRealHsm* self = (khRealHsm*)ptr;

	return k_redtbl_get(self->super, key, value);
}

//
void k_hsm_set_var(kHsm* ptr, const kVar* key, const kVar* value)
{
	khRealHsm* self = (khRealHsm*)ptr;

	k_redtbl_set(self->super, key, value);
}

// 라이브러리 테이블
static kRedTbl* _k_hsm_find_table(khRealHsm* self, const char* name)
{
	kuint sym;
	kVar kv, vv;
	kRedTbl* tbl;

	if (!name)
	{
		// 이름이 없으면 슈퍼
		return self->super;
	}

	name = k_symbothstt(name, &sym);
	k_var_set_sym(&kv, sym);

	if (!k_redtbl_get(self->super, &kv, &vv))
	{
		// 없으면 만듦
		tbl = k_redtbl_new();
		k_var_agn_type(&vv, KTYPE_TBL, (kBase*)tbl);
		k_redtbl_set(self->super, &kv, &vv);
	}
	else
	{
		if (!k_var_safe_type(&vv, KTYPE_TBL, (kBase**)&tbl))
		{
			// 테이블이 아니면 덮어씀
			tbl = k_redtbl_new();
			k_var_agn_type(&vv, KTYPE_TBL, (kBase*)tbl);
			k_redtbl_set(self->super, &kv, &vv);
		}
	}

	return tbl;
}

//
kint k_hsm_bind_libs(kHsm* ptr, const char* name, kint count, const struct khParamFuncReg* regs, kRedTbl** rettbl)
{
	khRealHsm* self = (khRealHsm*)ptr;
	kRedTbl* tbl;
	kint i, cnt;

	if (count > 0 && !regs)
	{
		if (rettbl)
			*rettbl = NULL;
		return -1;
	}

	tbl = _k_hsm_find_table(self, name);
	cnt = 0;

	k_mutex_enter(tbl->lock);

	for (i = 0; i < count; i++)
	{
		khParamDesc* prm;

		if (!regs[i].name)
			break;

		prm = kh_param_alloc_func(regs[i].name, regs[i].desc, regs[i].func, regs[i].data, regs[i].flags);

		if (!prm)
			continue;

		k_redtbl_unsafe_bind_param(tbl, prm, TRUE);
		cnt++;
	}

	k_mutex_leave(tbl->lock);

	if (rettbl)
		*rettbl = tbl;

	return cnt;
}

//
kcham k_hsm_bind_param(kHsm* ptr, const char* name, khParamDesc* prm)
{
	khRealHsm* self = (khRealHsm*)ptr;
	kRedTbl* tbl;

	k_return_value_if_fail(prm, FALSE);

	tbl = _k_hsm_find_table(self, name);

	return k_redtbl_bind_param(tbl, prm, TRUE);
}

//
kcham k_hsm_bind_func(kHsm* ptr, const char* name, kRedFn* rfn)
{
	khRealHsm* self = (khRealHsm*)ptr;
	kRedTbl* tbl;

	k_return_value_if_fail(rfn, FALSE);

	tbl = _k_hsm_find_table(self, name);

	return k_redtbl_bind_func(tbl, rfn);
}

//
kRedFn* k_hsm_compile(kHsm* ptr, const char* name, const char* script, const char* filename_N, const char* output_N, kint* error_N)
{
	khRealHsm* self = (khRealHsm*)ptr;
	kRedFn* rfn;
	kint tmp;
	kFile* file;

	k_return_value_if_fail(script, NULL);

	//
	file = output_N ? k_file_new(output_N, "w") : NULL;

	if (file)
	{
		char uname[260], cname[260];
		kint rv[4];
		kDateTime dt;

		k_getusername(uname, 259);
		k_getcomputername(cname, 259);
		k_version(rv);
		k_now(&dt);

		k_file_printf(file,
			"* Build: %04d-%02d-%02d %02d:%02d:%02d by %s [%s]\n"\
			"* Runtime: %d.%d.%d.%d\n",
			dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, uname, cname,
			rv[0], rv[1], rv[2], rv[3]);
	}

	//
	tmp = (*(const kint*)script) & 0x00FFFFFF;

	if (tmp == 0x00BFBBEF)
		script += 3;

	if (!filename_N)
		filename_N = "unknown";

	//
	k_hs_enter();

	if (!kh_parse_enter(filename_N, script, TRUE))
	{
		if (error_N)
			*error_N = 1;

		rfn = NULL;
	}
	else
	{
		self->parses++;
		kh_parse_write_file(file);

		if ((tmp = kh_compile_enter(kh_parse_get_token(), name, &rfn)) > 0)
		{
			if (error_N)
				*error_N = tmp;

			rfn = NULL;
		}
		else
		{
			if (error_N)
				*error_N = 0;

			self->compiles++;
			k_redfn_debug_file(rfn, file);

			kh_compile_leave();
		}

		kh_parse_leave();
	}

	//
	k_hs_leave();

	if (file)
		k_file_delete(file);

	return rfn;
}

kRedFn* k_hsm_compile_file(kHsm* ptr, const char* name, const char* filename, const char* output_N, kint* error_N)
{
	khRealHsm* self = (khRealHsm*)ptr;
	kRedFn* rfn;
	kint size;
	char* script;

	script = (char*)k_file_alloc(filename, &size);

	if (!script)
	{
		if (error_N)
			*error_N = 1;

		k_debug_error(TRUE, 0, 0, filename, 0x3FD, "compile", "cannot read script file.");

		return NULL;
	}

	script[size] = '\0';

	//
	rfn = k_hsm_compile((kHsm*)self, name, script, filename, output_N, error_N);

	k_delete(script);

	return rfn;
}

// 스택 데이터 준비
khRunInfo* k_hsm_alloc_run_info(kHsm* ptr, kRnh* run, kRedTbl* pub)
{
	khRealHsm* self = (khRealHsm*)ptr;
	khRunInfo* ri;

	k_mutex_enter(self->lock);

	if (self->rri)
	{
		ri = self->rri;
		self->rri = ri->next;
	}
	else
	{
		ri = k_new_1(khRunInfo);
		ri->data.size = 64;
		ri->data.data = k_new(64, kVar);
	}

	k_mutex_leave(self->lock);

	ri->next = NULL;
	ri->data.base = 0;
	ri->data.top = 2;
	ri->data.arity = 0;
	ri->data.aud = NULL;

	k_var_agn_type(&ri->data.data[0], KTYPE_RNH, (kBase*)run);
	k_var_agn_type(&ri->data.data[1], KTYPE_TBL, (kBase*)pub);

	k_base_load((kBase*)pub);

	return ri;
}

// 스택 데이터 제거
void k_hsm_free_run_info(kHsm* ptr, khRunInfo* ri)
{
	khRealHsm* self = (khRealHsm*)ptr;

	/*
	중대 안내
	: 1번 스택에는 public이 들어 있음
	: 스택 정보를 해제하지 않아 public이 해제되지 않을 경우
	: 메모리 유출이 발생함
	*/
	k_var_unload(&ri->data.data[1]);

	k_mutex_enter(self->lock);

	ri->next = self->rri;
	self->rri = ri;

	k_mutex_leave(self->lock);
}

// 콜 정보 준비
khCallInfo* k_hsm_alloc_call_info(kHsm* ptr, khRunInfo* ri, kRedFn* rfn, kint addr, kint base)
{
	khRealHsm* self = (khRealHsm*)ptr;
	khCallInfo* ci;

	k_mutex_enter(self->lock);

	if (self->rci)
	{
		ci = self->rci;
		self->rci = ci->next;
	}
	else
	{
		ci = k_new_1(khCallInfo);
	}

	k_mutex_leave(self->lock);

	ci->next = NULL;
	ci->prev = NULL;

	ci->rri = ri;
	ci->rfn = rfn;

	ci->addr = addr;
	ci->base = base;

	return ci;
}

// 콜 정보 제거
void k_hsm_free_call_info(kHsm* ptr, khCallInfo* ci)
{
	khRealHsm* self = (khRealHsm*)ptr;

	k_mutex_enter(self->lock);

	ci->next = self->rci;
	self->rci = ci;

	k_mutex_leave(self->lock);
}

// 러를
kRnh* k_hsm_create_run(kHsm* ptr, kRedFn* cfn, kRedTbl* pub_N/*=nullptr*/)
{
	khRealHsm* self = (khRealHsm*)ptr;
	khRealRnh* run;
	khRunInfo* ri;

	// pub 준비
	if (!pub_N)
		pub_N = self->super;
	else if (pub_N->parent != self->super)
	{
		k_base_pure_unload((kBase*)pub_N->parent);
		pub_N->parent = self->super;
		k_base_load((kBase*)self->super);
	}

	// run 준비
	run = (khRealRnh*)k_crbs_new(KTYPE_RNH);
	ri = k_hsm_alloc_run_info((kHsm*)self, (kRnh*)run, pub_N);

	run->hsm = (kHsm*)self;
	run->cfn = cfn;
	run->pub = pub_N;
	run->rri = ri;

	//
	k_base_load((kBase*)self);
	k_base_pure_load((kBase*)cfn);

	return (kRnh*)run;
}

// 러를르
kRnh* k_hsm_create_run_compile(kHsm* ptr, const char* script, kRedTbl* pub_N, const char* filename_N, const char* output_N, kint* error_N)
{
	khRealHsm* self = (khRealHsm*)ptr;
	khRealRnh* run;
	kRedFn* rfn;

	rfn = k_hsm_compile((kHsm*)self, NULL, script, filename_N, output_N, error_N);
	k_return_value_if_fail(rfn, NULL);

	run = (khRealRnh*)k_hsm_create_run((kHsm*)self, rfn, pub_N);

	k_base_unload((kBase*)rfn);

	return (kRnh*)run;
}

// 러를르르
kRnh* k_hsm_create_run_compile_file(kHsm* ptr, const char* filename, kRedTbl* pub_N, const char* output_N, kint* error_N)
{
	khRealHsm* self = (khRealHsm*)ptr;
	khRealRnh* run;
	kRedFn* rfn;

	rfn = k_hsm_compile_file((kHsm*)self, NULL, filename, output_N, error_N);
	k_return_value_if_fail(rfn, NULL);

	run = (khRealRnh*)k_hsm_create_run((kHsm*)self, rfn, pub_N);

	k_base_unload((kBase*)rfn);

	return (kRnh*)run;
}

// 라이브러리 등록
kcham k_hsm_reg_lib(kHsm* ptr, kuint libid)
{
	khRealHsm* self = (khRealHsm*)ptr;
	kssize_t i;

	for (i = 0; i < k_arr_count(&self->libs); i++)
	{
		if (k_arr_nth(&self->libs, i) == libid)
			return TRUE;
	}

	k_arr_add(kUintArr, &self->libs, libid);

	return FALSE;
}

// 만드르
kHsm* k_hsm_new(kint flags)
{
	khRealHsm* self = (khRealHsm*)k_crbs_new(KTYPE_HSM);

	self->flags |= flags;

	// 라이브러리 로드
	k_hlib_sys((kHsm*)self);
	k_hlib_math((kHsm*)self);

	return (kHsm*)self;
}


//////////////////////////////////////////////////////////////////////////
// 실행을 해봐염

//
extern kint _kh_exec_enter_call(khRealRnh* self, kint arity, kint addr);
extern kint _kh_exec_leave_call(khRealRnh* self);
extern kint _kh_exec_execute(khRealRnh* self);

//
kType k_rnh_type(void)
{
	return KTYPE_RNH;
}

//
static kpointer _k_rnh_disp(kBase* ptr)
{
	khRealRnh* self = (khRealRnh*)ptr;

	k_rnh_clean((kRnh*)self);

	k_hsm_free_run_info(self->hsm, self->rri);
	k_base_unload((kBase*)self->hsm);

	k_base_pure_unload((kBase*)self->cfn);

	return self;
}

//
void k_rnh_clean(kRnh* ptr)
{
	khRealRnh* self = (khRealRnh*)ptr;
	kScv* s = &self->rri->data;

	khCallInfo* prsn;
	khCallInfo* prev;
	ksize_t i;

	for (prsn = self->rci; prsn; prsn = prev)
	{
		prev = prsn->prev;

		k_var_unload(&s->data[s->base - 2]);
		k_var_unload(&s->data[s->base - 1]);

		for (i = s->base; i < s->top; i++)
			k_var_unload(&s->data[i]);

		s->top = s->base - 2;
		s->base = prsn->base;

		k_hsm_free_call_info(self->hsm, prsn);
	}
}

//
static void _k_rnh_do_ret_val(kScv* s, kVar* ret)
{
	if (ret)
		k_scv_pop_ret(s, ret);
	else
	{
		kVar v;
		k_scv_pop_ret(s, &v);
		k_var_unload(&v);
	}
}

#if 0
//
static kcham _k_rnh_do_check_arity(khRealRnh* self, kScv* s, kint arity)
{
	// 헐..
	return TRUE;
}
#endif

// 실행
kcham k_rnh_run(kRnh* ptr, kRedFn* fn, kVar* retvar)
{
	khRealRnh* self = (khRealRnh*)ptr;
	kScv* s = &self->rri->data;

	if (!fn)
	{
		k_return_value_if_fail(self->cfn, FALSE);
		fn = self->cfn;
	}

	k_scv_push_null(s);
	k_scv_push_type(s, KTYPE_FN, (kBase*)fn);

	if (_kh_exec_enter_call(self, 0, 0) != 0)
		return FALSE;

	if (_kh_exec_execute(self) != KRR_OK)
		return FALSE;

	_k_rnh_do_ret_val(s, retvar);

	return TRUE;
}

// 메인 실행
kcham k_rnh_run_main(kRnh* ptr, kVar* retvar)
{
	return k_rnh_run(ptr, NULL, retvar);
}

// 준비
kScv* k_rnh_prepare(kRnh* ptr, const char* lookup, const kVar* self_nullable)
{
	khRealRnh* self = (khRealRnh*)ptr;
	kScv* s;
	kVar fnv;

	if (lookup)
	{
		if (!k_redtbl_find(self->pub, lookup, &fnv))
			return NULL;
	}
	else
	{
		k_return_value_if_fail(self->cfn, NULL);
		k_var_agn_type(&fnv, KTYPE_FN, (kBase*)self->cfn);
	}

	s = &self->rri->data;

	if (self_nullable)
		k_scv_push(s, self_nullable);
	else
		k_scv_push_null(s);

	k_scv_push(s, &fnv);

	return s;
}

// 런치
kcham k_rnh_launch(kRnh* ptr, kint arity, kVar* retvar_nullable)
{
	khRealRnh* self = (khRealRnh*)ptr;
	kScv* s = &self->rri->data;
	kint ret;

	ret = _kh_exec_enter_call(self, arity, 0);

	if (ret == KRR_END)
	{
		// C 함수
		_k_rnh_do_ret_val(s, retvar_nullable);

		return TRUE;
	}
	else if (ret == KRR_ERROR)
	{
		// 예외
		if (retvar_nullable)
			k_var_zero(retvar_nullable);

		return FALSE;
	}
	else
	{
		// 실행
		ret = _kh_exec_execute(self);

		if (ret == KRR_ERROR)
		{
			if (retvar_nullable)
				k_var_zero(retvar_nullable);

			return FALSE;
		}
		else
		{
			_k_rnh_do_ret_val(s, retvar_nullable);

			return TRUE;
		}
	}
}

// 타입 등록
static void _k_hs_type_init(void)
{
	// HSM
	static const kTypeDecl hsm_decl =
	{
		sizeof(khRealHsm), KTIF_SEALED,
		NULL, NULL, _k_hsm_cnst, _k_hsm_disp,
	};
	// RUN
	static const kTypeDecl hsp_desc =
	{
		sizeof(khRealRnh), KTIF_SEALED,
		NULL, NULL, NULL, _k_rnh_disp,
	};

	//
	k_tmnt_reg_red(KTYPE_HSM, "Hsm", &hsm_decl, KTYPE_BASE);
	k_tmnt_reg_red(KTYPE_RNH, "Hsp", &hsp_desc, KTYPE_BASE);
}
